Implement a command to list owners of a crate
authorAlex Crichton <alex@alexcrichton.com>
Thu, 13 Nov 2014 06:15:41 +0000 (22:15 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 13 Nov 2014 06:32:23 +0000 (22:32 -0800)
Closes #858

src/bin/owner.rs
src/cargo/ops/mod.rs
src/cargo/ops/registry.rs
src/registry/lib.rs

index efa1d9ba7dbff17981c55a4674c0b246f349affd..8c64ecbfd498f78f2e7a0c94aa44dd9d70f6bd6b 100644 (file)
@@ -11,6 +11,7 @@ struct Options {
     flag_remove: Option<Vec<String>>,
     flag_index: Option<String>,
     flag_verbose: bool,
+    flag_list: bool,
 }
 
 pub const USAGE: &'static str = "
@@ -23,6 +24,7 @@ Options:
     -h, --help              Print this message
     -a, --add LOGIN         Login of a user to add as an owner
     -r, --remove LOGIN      Login of a user to remove as an owner
+    -l, --list              List owners of a crate
     --index INDEX           Registry index to modify owners for
     --token TOKEN           API token to use when authenticating
     -v, --verbose           Use verbose output
@@ -35,12 +37,15 @@ versions, and also modify the set of owners, so take caution!
 pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
     shell.set_verbose(options.flag_verbose);
     let root = try!(find_root_manifest_for_cwd(None));
-    try!(ops::modify_owners(&root, shell,
-                            options.arg_crate,
-                            options.flag_token,
-                            options.flag_index,
-                            options.flag_add,
-                            options.flag_remove).map_err(|e| {
+    let opts = ops::OwnersOptions {
+        krate: options.arg_crate,
+        token: options.flag_token,
+        index: options.flag_index,
+        to_add: options.flag_add,
+        to_remove: options.flag_remove,
+        list: options.flag_list,
+    };
+    try!(ops::modify_owners(&root, shell, &opts).map_err(|e| {
         CliError::from_boxed(e, 101)
     }));
     Ok(None)
index 5a083bda178dd14efcd1155ce731681cb55620d4..885a15e2b8daea810d56910a125f38dab2d00d0f 100644 (file)
@@ -18,7 +18,7 @@ pub use self::cargo_test::{run_tests, run_benches, TestOptions};
 pub use self::cargo_package::package;
 pub use self::registry::{publish, registry_configuration, RegistryConfig};
 pub use self::registry::{registry_login, http_proxy, http_handle};
-pub use self::registry::{modify_owners, yank};
+pub use self::registry::{modify_owners, yank, OwnersOptions};
 pub use self::cargo_fetch::{fetch};
 pub use self::cargo_pkgid::pkgid;
 pub use self::resolve::{resolve_pkg, resolve_with_previous};
index f5e9944578c765da753aa75c5b9ae29dba7e1369..16f5f6397de6babb29ef3ff1cb2b649d20967a0d 100644 (file)
@@ -218,15 +218,20 @@ pub fn registry_login(shell: &mut MultiShell, token: String) -> CargoResult<()>
     config::set_config(&config, config::Global, "registry", config::Table(map))
 }
 
+pub struct OwnersOptions {
+    pub krate: Option<String>,
+    pub token: Option<String>,
+    pub index: Option<String>,
+    pub to_add: Option<Vec<String>>,
+    pub to_remove: Option<Vec<String>>,
+    pub list: bool,
+}
+
 pub fn modify_owners(manifest_path: &Path,
                      shell: &mut MultiShell,
-                     krate: Option<String>,
-                     token: Option<String>,
-                     index: Option<String>,
-                     to_add: Option<Vec<String>>,
-                     to_remove: Option<Vec<String>>) -> CargoResult<()> {
-    let name = match krate {
-        Some(name) => name,
+                     opts: &OwnersOptions) -> CargoResult<()> {
+    let name = match opts.krate {
+        Some(ref name) => name.clone(),
         None => {
             let mut src = try!(PathSource::for_path(&manifest_path.dir_path()));
             try!(src.update());
@@ -235,10 +240,11 @@ pub fn modify_owners(manifest_path: &Path,
         }
     };
 
-    let (mut registry, _) = try!(registry(shell, token, index));
+    let (mut registry, _) = try!(registry(shell, opts.token.clone(),
+                                          opts.index.clone()));
 
-    match to_add {
-        Some(v) => {
+    match opts.to_add {
+        Some(ref v) => {
             let v = v.iter().map(|s| s.as_slice()).collect::<Vec<_>>();
             try!(shell.status("Owner", format!("adding `{:#}` to `{}`", v, name)));
             try!(registry.add_owners(name.as_slice(), v.as_slice()).map_err(|e| {
@@ -248,8 +254,8 @@ pub fn modify_owners(manifest_path: &Path,
         None => {}
     }
 
-    match to_remove {
-        Some(v) => {
+    match opts.to_remove {
+        Some(ref v) => {
             let v = v.iter().map(|s| s.as_slice()).collect::<Vec<_>>();
             try!(shell.status("Owner", format!("removing `{:#}` from `{}`",
                                                v, name)));
@@ -260,6 +266,21 @@ pub fn modify_owners(manifest_path: &Path,
         None => {}
     }
 
+    if opts.list {
+        let owners = try!(registry.list_owners(name.as_slice()).map_err(|e| {
+            human(format!("failed to list owners: {}", e))
+        }));
+        for owner in owners.iter() {
+            print!("{}", owner.login);
+            match (owner.name.as_ref(), owner.email.as_ref()) {
+                (Some(name), Some(email)) => println!(" ({} <{}>)", name, email),
+                (Some(s), None) |
+                (None, Some(s)) => println!(" ({})", s),
+                (None, None) => println!(""),
+            }
+        }
+    }
+
     Ok(())
 }
 
index 2c5938c6b6c6bf8e4952c82a11a257b81b1e2be3..9c58160bf784fcbf46e233ae42d205d17b462008 100644 (file)
@@ -8,6 +8,7 @@ use std::io::util::ChainedReader;
 use std::result;
 
 use curl::http;
+use curl::http::handle::{Put, Get, Delete, Method, Request};
 use serialize::json;
 
 pub struct Registry {
@@ -53,10 +54,20 @@ pub struct NewCrateDependency {
     pub target: Option<String>,
 }
 
+#[deriving(Decodable)]
+pub struct User {
+    pub id: uint,
+    pub login: String,
+    pub avatar: String,
+    pub email: Option<String>,
+    pub name: Option<String>,
+}
+
 #[deriving(Decodable)] struct R { ok: bool }
 #[deriving(Decodable)] struct ApiErrorList { errors: Vec<ApiError> }
 #[deriving(Decodable)] struct ApiError { detail: String }
 #[deriving(Encodable)] struct OwnersReq<'a> { users: &'a [&'a str] }
+#[deriving(Decodable)] struct Users { users: Vec<User> }
 
 impl Registry {
     pub fn new(host: String, token: String) -> Registry {
@@ -88,6 +99,11 @@ impl Registry {
         Ok(())
     }
 
+    pub fn list_owners(&mut self, krate: &str) -> Result<Vec<User>> {
+        let body = try!(self.get(format!("/crates/{}/owners", krate)));
+        Ok(json::decode::<Users>(body.as_slice()).unwrap().users)
+    }
+
     pub fn publish(&mut self, krate: &NewCrate, tarball: &Path) -> Result<()> {
         let json = json::encode(krate);
         // Prepare the body. The format of the upload request is:
@@ -135,19 +151,25 @@ impl Registry {
     }
 
     fn put(&mut self, path: String, b: &[u8]) -> Result<String> {
-        handle(self.handle.put(format!("{}/api/v1{}", self.host, path), b)
-                          .header("Authorization", self.token.as_slice())
-                          .header("Accept", "application/json")
-                          .content_type("application/json")
-                          .exec())
+        self.req(path, Some(b), Put)
+    }
+
+    fn get(&mut self, path: String) -> Result<String> {
+        self.req(path, None, Get)
     }
 
     fn delete(&mut self, path: String, b: Option<&[u8]>) -> Result<String> {
-        let mut req = self.handle.delete(format!("{}/api/v1{}", self.host, path))
-                                 .header("Authorization", self.token.as_slice())
-                                 .header("Accept", "application/json")
-                                 .content_type("application/json");
-        match b {
+        self.req(path, b, Delete)
+    }
+
+    fn req(&mut self, path: String, body: Option<&[u8]>,
+           method: Method) -> Result<String> {
+        let mut req = Request::new(&mut self.handle, method)
+                              .uri(format!("{}/api/v1{}", self.host, path))
+                              .header("Authorization", self.token.as_slice())
+                              .header("Accept", "application/json")
+                              .content_type("application/json");
+        match body {
             Some(b) => req = req.body(b),
             None => {}
         }